package cn.highset.redis.service;

import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.io.Closeable;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author gg
 * @version RedisKeyService.java, v 0.1 2019-04-09 16:52 gg
 */
public class RedisKeyService extends RedisBaseService {

    private RedisTemplate<String, Object> redisTemplate;

    public RedisKeyService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 通过通配符获取 key集合
     *
     * @param parttern
     * @return
     */
    public Set<String> keys(final String parttern) {
        return redisTemplate.keys(parttern);
    }

    /**
     * 对应 del 命令 删除key
     *
     * @param key key
     */
    public void del(final String key) {
        checkKey(key);
        redisTemplate.delete(key);
    }

    /**
     * 对应 del 命令 删除key
     *
     * @param keys key
     */
    public void del(final Set<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 对应 dump 命令
     * 序列化给定key，并返回被序列化的值，使用 restore 命令可以将这个值反序列化为Redis键
     * 序列化生成的值有以下几个特点：
     * 1.它带有64位的校验和，用于检测错误，restore 在进行反序列化之前会先检查校验和。
     * 2.值的编码格式和RDB文件保持一致。
     * 3.RDB版本会被编码在序列化值当中，如果因为Redis的版本不同造成RDB格式不兼容，那么Redis会拒绝对这个值进行反序列化操作。
     * 序列化的值不包括任何生存时间信息
     *
     * @param key key
     * @return 如果key不存在，那么返回null。否则，返回序列化之后的值
     */
    public byte[] dump(final String key) {
        checkKey(key);
        return redisTemplate.dump(key);
    }

    /**
     * 对应命令 exists 检查key是否存在
     *
     * @param key key
     * @return 若key存在，返回1，否则返回0
     */
    public Boolean exists(final String key) {
        checkKey(key);
        return redisTemplate.hasKey(key);
    }

    /**
     * 对应命令 type 返回键的类型
     *
     * @param key key
     * @return NONE(" none "), STRING("string"), LIST("list"), SET("set"), ZSET("zset"), HASH("hash");
     */
    public String type(final String key) {
        checkKey(key);
        DataType type = redisTemplate.type(key);
        return type.code();
    }

    /**
     * 对应命令 rename
     * <p>
     * 将 key 改名为 newKey
     * 当 key 和 newKey 相同，或者 key 不存在时，返回一个错误
     * 当 newKey 已经存在时， rename 命令将覆盖旧值
     *
     * @param oldKey 旧key
     * @param newKey 新key
     */
    public void rename(final String oldKey, final String newKey) {
        checkKey(oldKey);
        checkKey(newKey);
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * 对应命令 renameNx
     * 当且仅当 新key 不存在时，将 key 改名为 新key 。
     * 当 key 不存在时，返回一个错误
     *
     * @param oldKey 旧key
     * @param newKey 新key
     * @return 修改成功时，返回 1 ； 如果 newKey 已经存在，返回 0
     */
    public Boolean renameNx(final String oldKey, final String newKey) {
        checkKey(oldKey);
        checkKey(newKey);
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 对应move 命令
     * 将当前数据库的 key 移动到给定的数据库 db 当中
     * 如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ，或者 key 不存在于当前数据库，那么 MOVE 没有任何效果。
     * 因此，也可以利用这一特性，将 MOVE 当作锁(locking)原语(primitive)
     *
     * @param key     key
     * @param dbIndex db
     * @return 移动成功返回 1 ，失败则返回 0
     */
    public Boolean move(final String key, final int dbIndex) {
        checkKey(key);
        return redisTemplate.move(key, dbIndex);
    }

    /**
     * 对应命令 randomKey
     * 从当前数据库中随机返回(不删除)一个 key
     *
     * @return 当数据库不为空时，返回一个 key 。 当数据库为空时，返回 null
     */
    public String randomKey() {
        return redisTemplate.randomKey();
    }


    /**
     * 对应scan 命令 详情请看 RedisHashService 的 hScan 方法
     * 此项目使用1.8.1 不需要手动关闭
     *
     * @param pattern 匹配规则
     * @param limit   游标
     * @return Cursor
     */
    public Cursor<String> scan(final String pattern, int limit) {
        checkPattern(pattern);
        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(limit).build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();

        return (Cursor<String>) redisTemplate.executeWithStickyConnection(
                (RedisCallback<Closeable>) redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize)
        );
    }

    /**
     * 对应sort 命令
     * 返回或保存给定列表、集合、有序集合 key 中经过排序的元素。
     * 排序默认以数字作为对象，值被解释为双精度浮点数，然后进行比较
     * 查询条件构造：
     * SortQuery<String> query = SortQueryBuilder.sort(key)    排序的key
     * .by("pattern")       key的正则过滤
     * .noSort()            不使用正则过滤key
     * .get("2")            在value里过滤正则，可以连续写多个get
     * .limit(0, 5)         分页，和mysql一样
     * .order(Order.DESC)   正序or倒序
     * .alphabetical(true)  ALPHA修饰符用于对字符串进行排序，false的话只针对数字排序
     * .build();
     *
     * @param query 查询条件
     * @return List<Object>
     */
    public List<Object> sort(SortQuery<String> query) {
        return redisTemplate.sort(query);
    }

    /**
     * 设置过期时间
     * 为给定 key 设置生存时间，当 key 过期时(生存时间为 0 )，它会被自动删除。
     * 在 Redis 中，带有生存时间的 key 被称为『易失的』(volatile)
     * <p>
     * 在 Redis 2.4 版本中，过期时间的延迟在 1 秒钟之内 —— 也即是，就算 key 已经过期，
     * 但它还是可能在过期之后一秒钟之内被访问到，而在新的 Redis 2.6 版本中，延迟被降低到 1 毫秒之内
     * <p>
     * 使用 persist 命令可以在不删除 key 的情况下，移除 key 的生存时间，让 key 重新成为一个『持久的』(persistent) key
     *
     * @param key     key
     * @param timeout 过期时间
     * @param unit    时间单位
     *                NANOSECONDS 纳秒
     *                MICROSECONDS  微秒
     *                MILLISECONDS 毫秒
     *                SECONDS 秒
     *                MINUTES 分钟
     *                HOURS 小时
     *                DAYS  天
     * @return 设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间)，返回 0
     */
    public Boolean expire(final String key, final long timeout, final TimeUnit unit) {
        checkKey(key);
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 指定时间过期
     * jedis 实际操作的是时间戳
     *
     * @param key  key
     * @param date Date
     * @return 如果生存时间设置成功，返回 1 ； 当 key 不存在或没办法设置生存时间，返回 0
     */
    public Boolean expireAt(final String key, final Date date) {
        checkKey(key);
        return redisTemplate.expireAt(key, date);
    }

    /**
     * 移除给定 key 的生存时间，将这个 key 从“易失的”(带生存时间 key )转换成“持久的”(一个不带生存时间、永不过期的 key )。
     *
     * @param key key
     * @return 当生存时间移除成功时，返回 1 . 如果 key 不存在或 key 没有设置生存时间，返回 0
     */
    public Boolean persist(final String key) {
        checkKey(key);
        return redisTemplate.persist(key);
    }


}